#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Author:  Nando Florestan -- http://oui.com.br
# License: LGPL

from __future__ import absolute_import
# http://docs.python.org/whatsnew/pep-328.html

from datetime import datetime
from cherrypy import request
from sqlalchemy import desc, Table
from sqlalchemy.orm import synonym, mapper
from . import ShowUserThisException, warn_deprecated
from .sqlalchemy_tool import metadata, session


def capitalize(s):
    if s is None:  return None
    l = len(s)
    if l == 0:  return s
    if l == 1:  return s.upper()
    return s[0].upper() + s[1:]


def make_BaseModel(session):
    class BaseModel(object):
        """Useful superclass for SQLAlchemy models."""
        def set(self, **k):
            """Sets many attributes at once.
            First updates the 'changed' field to current time.
            Also calls validate(**k) if the model has such a method.
            The validate() method must return the k dict
            (that's an opportunity to change it).
            """
            if hasattr(self, b"changed"):  self.changed = datetime.utcnow()
            if hasattr(self, b'validate'):  k = self.validate(**k)
            for k, v in k.iteritems():
                if hasattr(self, k):
                    setattr(self, k, v)
                else:
                    raise TypeError \
                        ('%r is an invalid keyword argument for %s' %
                                    (k, type(self).__name__))
        
        def save(self, **k):
            warn_deprecated("Don't use BaseModel.save().")
            self.set(**k)
            session.add(self)

        @classmethod
        def put(cls, id, dic={}, session=session, flush=False):
            """Class method that updates an object --
            or creates a new one if id=="0". Returns a tuple (o, is_new) where:
            * o is the persisted object, and
            * is_new is a boolean informing whether o was added or updated.
            
            For very simple controllers, using this method can save you a few
            lines. Otherwise it is better to be explicit!
            """
            is_new = id=='0' or not id
            if is_new:
                o = cls.new(**dic) if hasattr(cls, "new") else cls(**dic)
                session.add(o)
            else:
                o = session.query(cls).get(id)
                o.set(**dic)
            if flush: session.flush()
            return o, is_new
        
        # query = session.query_property()
        
        @classmethod
        def all(cls):
            return session.query(cls).all()
        
        @classmethod
        def get(cls, id):
            return session.query(cls).get(id)
        
        def expunge(self):
            session.expunge(self)
        
        @classmethod
        def count(cls, filter=None):
            if filter is None:
                return session.query(cls).count()
            else:
                return session.query(cls).filter(filter).count()
        
        @classmethod
        def get_last(cls):
            '''Returns the object with the largest id.'''
            return session.query(cls).order_by(desc(cls.id)).first()
    
    return BaseModel


BaseModel = make_BaseModel(session)



def tables_in(adict, SABase=None):
    '''Returns a list containing the tables in the context *adict*.
    If you are using declarative, provide your *SABase* too.
    Usage:  _tables = tables_in(globals(), MySABase)
    '''
    tables = []
    for val in adict.values():
        if SABase and hasattr(val, '__base__') and val.__base__ == SABase:
            tables.append(val.__table__)
        elif isinstance(val, Table):
            tables.append(val)
    return tables

def recreate_tables(metadata=metadata, tables=None, dropfirst=True):
    if dropfirst: metadata.drop_all(tables=tables, bind=session.bind)
    metadata.create_all(tables=tables, bind=session.bind)

def prop_strip_require(attribname, doc=None):
    """Returns a property whose setter:
    1) strips (trims) the value;
    2) raises a ShowUserThisException if that is an empty string;
    3) sets the private attribute
    """
    def fget(self):
        return getattr(self, attribname)
    def fset(self, val):
        v = val.strip()
        if v == "":
            raise ShowUserThisException(_(u"%s não pode ficar em branco.") \
                                        % attribname)
        setattr(self, attribname, v)
    return property(fget, fset, doc)

def synonym_strip_require(attribname, doc=None):
    return synonym(attribname, descriptor=prop_strip_require(attribname, doc))

def prop_strip_require_capitalize(attribname, doc=None):
    """Returns a property whose setter:
    1) strips (trims) the value;
    2) raises a ShowUserThisException if that is an empty string;
    3) capitalizes the value;
    4) sets the private attribute
    """
    def fget(self):
        return getattr(self, attribname)
    def fset(self, val):
        v = val.strip()
        if v == "":
            raise ShowUserThisException(_(u"%s não pode ficar em branco.") \
                                        % attribname)
        setattr(self, attribname, capitalize(v))
    return property(fget, fset, doc)

def synonym_strip_require_capitalize(attribname, doc=None):
    return synonym(attribname,
                   descriptor=prop_strip_require_capitalize(attribname, doc))

def prop_generic(attribname, doc=None, setfilter = lambda x: x):
    """Returns a property encapsulating *attribname*, with the optional
    docstring *doc*, and the optional *setfilter* function.
    """
    def fget(self):
        return getattr(self, attribname)
    def fset(self, val):
        setattr(self, attribname, setfilter(val))
    return property(fget, fset, doc)

def synonym_generic(attribname, doc=None, setfilter=None):
    """Returns a synonym with optional *doc* and *setfilter*."""
    def fget(self):
        return getattr(self, attribname)
    if setfilter:
        def fset(self, val):
            setattr(self, attribname, setfilter(val))
    else:
        def fset(self, val):
            setattr(self, attribname, val)
    return synonym(attribname, descriptor=property(fget, fset, doc))

def prop_strip_capitalize(attribname, doc=None):
    """Returns a property whose setter:
    
    1) strips (trims) the value;
    2) capitalizes the value;
    3) sets the private attribute.
    """
    return prop_generic(attribname,
                        setfilter = lambda x: capitalize(x.strip()))

def synonym_strip_capitalize(attribname, doc=None):
    return synonym(attribname,
                   descriptor=prop_strip_capitalize(attribname, doc))

def prop_strip_lowercase(attribname, doc=None):
    """Returns a property whose setter:
    
    1) strips (trims) the value;
    2) makes it all lowercase;
    3) sets the private attribute.
    """
    return prop_generic(attribname,
                        setfilter = lambda x: x.strip().lower())

def synonym_strip_lowercase(attribname, doc=None):
    return synonym(attribname,
                   descriptor=prop_strip_lowercase(attribname, doc))



# TYPES FOR SQLITE
# ================
import sqlalchemy.types as types
class AutoDate(types.TypeDecorator):
    """A SQLAlchemy DateTime type that converts strings to datetime
    when storing. Prevents TypeError("SQLite Date, Time, and DateTime types
    only accept Python datetime objects as input.")
    """
    impl = types.DateTime
    
    def process_bind_param(self, value, dialect):
        if isinstance(value, basestring):
            if value == '':  return None
            parts = value.split('.')
            dt = datetime.strptime(parts[0], "%Y-%m-%d %H:%M:%S")
            if len(parts) > 1:
                dt.replace(microsecond=int(parts[1]))
            return dt
        else:
            return value
    '''
    def process_result_value(self, value, dialect):
        return value[7:]
    
    def copy(self):
        return Dayte(self.impl.length)
    '''

class Integer(types.TypeDecorator):
    impl = types.Integer
    
    def process_bind_param(self, value, dialect):
        '''Make sure an int is persisted. Otherwise, SQLite might persist
        things such as empty strings...
        '''
        return None if value is None else int(value)
        #return None if not value else int(value)
del types
